using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
using Developex.DatabaseWrapper.Attributes;
using Developex.DatabaseWrapper.Fields;
using Developex.DatabaseWrapper.Secure;

namespace Developex.DatabaseWrapper
{
	/// <summary>
	/// db row (entity)
	/// </summary>
	public abstract class Row : ICloneable
	{
		#region Mapping

		#region Public

		///<summary>
		/// Get field by property name
		///</summary>
		///<param name="propertyName"></param>
		///<returns></returns>
		public IField GetFieldByPropertyName(Enum propertyName)
		{
			return GetFieldByPropertyName(propertyName.ToString());
		}

		///<summary>
		/// Get field by property name
		///</summary>
		///<param name="propertyName"></param>
		///<returns></returns>
		public IField GetFieldByPropertyName(string propertyName)
		{
			int k = propertyName.IndexOf(Connection.FieldNameDelimiter);
			if (k == -1)
			{
				string fieldName = GetFieldNameByPropertyName(propertyName);
				return Fields.ContainsKey(fieldName) ? Fields[fieldName] : null;
			}
			string singlePropertyName = propertyName.Substring(0, k);
			string singlFieldName = GetFieldNameByPropertyName(singlePropertyName);
			if (string.IsNullOrEmpty(singlFieldName) || !Fields.ContainsKey(singlFieldName))
				return null;
			IForeignObject ff = Fields[singlFieldName] as IForeignObject;
			if (ff == null)
				return null;
			return ff.GetForeignObject().GetFieldByPropertyName(propertyName.Substring(k + 1));
		}

		/// <summary>
		/// Get field name by property name
		/// </summary>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		public virtual string GetFieldNameByPropertyName(string propertyName)
		{
			Dictionary<string, FieldInfo> map = PropertyToFieldMap;
			if (map == null || !map.ContainsKey(propertyName))
				return null;
			return ((IField)map[propertyName].GetValue(this)).Name;
		}

		///<summary>
		///</summary>
		///<param name="propertyNames"></param>
		///<returns></returns>
		///<exception cref="InvalidOperationException"></exception>
		public List<IField> GetFieldsByPropertyNames(IEnumerable propertyNames)
		{
			List<IField> fields = new List<IField>();
			foreach (object propertyNameObject in propertyNames)
			{
				string propertyName = propertyNameObject.ToString();
				IField field = GetFieldByPropertyName(propertyName);
				if (field == null)
					throw new InvalidOperationException("Cannot find field with property name = " + propertyName);
				fields.Add(field);
			}
			return fields;
		}

		///<summary>
		///</summary>
		///<param name="propertyNames"></param>
		///<returns></returns>
		///<exception cref="InvalidOperationException"></exception>
		public List<IField> GetFieldsByPropertyNames(IEnumerable<string> propertyNames)
		{
			List<IField> fields = new List<IField>();
			foreach (string propertyName in propertyNames)
			{
				IField field = GetFieldByPropertyName(propertyName);
				if (field == null)
					throw new InvalidOperationException("Cannot find field with property name = " + propertyName);
				fields.Add(field);
			}
			return fields;
		}

		/// <summary>
		/// Mapping: [Property Name, FieldInfo]
		/// </summary>
		public Dictionary<string, FieldInfo> PropertyToFieldMap
		{
			get
			{
				EnsurePropertyToFieldMap();
				return _PropertyToFieldMap;
			}
		}

		/// <summary>
		/// Mapping: [Property Name, FieldInfo]
		/// </summary>
		public Dictionary<string, FieldInfo> FieldNameToFieldMap
		{
			get
			{
				EnsureFieldNameToFieldMap();
				return _FieldNameToFieldMap;
			}
		}

		#endregion

		#region Implementation

		/// <summary>
		/// For each type: [Property Name, FieldInfo]
		/// </summary>
		private static readonly Dictionary<Type, Dictionary<string, FieldInfo>> GlobalPropertyToFieldMap = new Dictionary<Type, Dictionary<string, FieldInfo>>();

		/// <summary>
		/// For each type: [Field Name, FieldInfo]
		/// </summary>
		private static readonly Dictionary<Type, Dictionary<string, FieldInfo>> GlobalFieldNameToFieldMap = new Dictionary<Type, Dictionary<string, FieldInfo>>();

		private Dictionary<string, FieldInfo> _PropertyToFieldMap;
		private Dictionary<string, FieldInfo> _FieldNameToFieldMap;

		private void EnsurePropertyToFieldMap()
		{
			if (_PropertyToFieldMap == null)
			{
				lock (GlobalPropertyToFieldMap)
				{
					Type type = GetType();
					if (!GlobalPropertyToFieldMap.ContainsKey(type))
					{
						_PropertyToFieldMap = new Dictionary<string, FieldInfo>();
						FillPropertyToFieldMap(type, _PropertyToFieldMap);
						GlobalPropertyToFieldMap.Add(type, _PropertyToFieldMap);
					}
					else _PropertyToFieldMap = GlobalPropertyToFieldMap[type];
				}
			}
		}

		private void EnsureFieldNameToFieldMap()
		{
			if (_FieldNameToFieldMap == null)
			{
				lock (GlobalFieldNameToFieldMap)
				{
					Type type = GetType();
					if (!GlobalFieldNameToFieldMap.ContainsKey(type))
					{
						_FieldNameToFieldMap = new Dictionary<string, FieldInfo>();
						FillFieldNameToFieldMap(type, _FieldNameToFieldMap);
						GlobalFieldNameToFieldMap.Add(type, _FieldNameToFieldMap);
					}
					else _FieldNameToFieldMap = GlobalFieldNameToFieldMap[type];
				}
			}
		}

		private void FillPropertyToFieldMap(Type type, IDictionary<string, FieldInfo> map)
		{
			FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.FieldType.GetInterface("IField") != typeof(IField))
					continue;
				object[] attributes = fieldInfo.GetCustomAttributes(false);
				foreach (PropertyNameAttribute attribute in attributes)
				{
					map.Add(attribute.PropertyName, fieldInfo);
				}
			}
			if (type.BaseType != typeof(Row))
				FillPropertyToFieldMap(type.BaseType, map);
		}

		private void FillFieldNameToFieldMap(Type type, IDictionary<string, FieldInfo> map)
		{
			FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			foreach (FieldInfo fieldInfo in fields)
			{
				IField field = fieldInfo.GetValue(GetSampleItem()) as IField;
				if (field == null || string.IsNullOrEmpty(field.Name))
					continue;
				map.Add(field.Name, fieldInfo);
			}
			if (type.BaseType != typeof(Row))
				FillFieldNameToFieldMap(type.BaseType, map);
		}

		#endregion

		#endregion

		#region Abstract members

		///<summary>
		/// Add field by it's enum
		///</summary>
		///<param name="propertyName"></param>
		public abstract void CreateAndAddField(Enum propertyName);

		/// <summary>
		/// Create and add field by mapped property name
		/// </summary>
		/// <param name="propertyName"></param>
		public abstract void CreateAndAddField(string propertyName);

		///<summary>
		/// Create object for clone
		///</summary>
		///<returns></returns>
		/// <remarks>
		/// NB!: don't add any fields here
		/// Here should be only object creation, all data copy should be in overrided Clone function.
		/// Used only in base version of Clone function.
		/// Should be overrided for ICloneable interface.
		/// </remarks>
		protected abstract Row CreateObjectForClone();

		/// <summary>
		/// get sample item
		/// </summary>
		/// <returns></returns>
		protected abstract Row GetSampleItem();

		#endregion

		#region Constructor

		private readonly string tableName;
		private string tableAlias;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="tableName"></param>
		protected internal Row(string tableName)
		{
			LastException = null;
			this.tableName = tableName;
		}

		#endregion

		#region Public members/properties

		/// <summary>
		/// Fields, key is field name
		/// </summary>
		[NonSerialized]
		protected internal Dictionary<string, IField> Fields = new Dictionary<string, IField>();

		///<summary>
		/// List of Fields
		///</summary>
		public List<IField> FieldsList { get { return new List<IField>(Fields.Values); } }

		[NonSerialized]
		private List<IChildValuesField> childValuesFields;

		/// <summary>
		/// Child objects
		/// </summary>        
		public List<IChildValuesField> ChildValuesFields
		{
			get
			{
				if (childValuesFields == null)
					childValuesFields = new List<IChildValuesField>();
				return childValuesFields;
			}
		}

		/// <summary>
		/// Table name
		/// </summary>
		public string TableName
		{
			get { return tableName; }
		}

		/// <summary>
		/// Table name
		/// </summary>
		public string TableName_br
		{
			get { return "[" + tableName + "]"; }
		}

		/// <summary>
		/// Table alias
		/// </summary>
		public string TableAlias
		{
			get { return tableAlias; }
			set { tableAlias = value; }
		}

		/// <summary>
		/// Get primary key field
		/// </summary>
		public IField PrimaryKeyField
		{
			get
			{
				if (PrimaryKeyFields.Length == 1)
					return primaryKeyFields[0];
				return null;
			}
		}

		private IField[] primaryKeyFields;

		/// <summary>
		/// Get primary key field
		/// </summary>
		public IField[] PrimaryKeyFields
		{
			get
			{
				if (primaryKeyFields == null)
				{
					List<IField> primaryFields = new List<IField>();
					foreach (KeyValuePair<string, IField> pair in Fields)
					{
						if ((pair.Value.Flags & FieldFlags.PrimaryKey) != 0)
							primaryFields.Add(pair.Value);
					}
					primaryKeyFields = primaryFields.ToArray();
				}
				return primaryKeyFields;
			}
		}

		/// <summary>
		/// Get last exception
		/// </summary>
		/// <returns></returns>
		public DatabaseException LastException { get; set; }

		Row parentObject;
		IForeignObject parentField;

		/// <summary>
		/// parent object
		/// </summary>
		public Row ParentObject
		{
			get { return parentObject; }
		}

		/// <summary>
		/// parent field
		/// </summary>
		public IForeignObject ParentField
		{
			get { return parentField; }
		}

		/// <summary>
		/// Set parent
		/// </summary>
		/// <param name="parent"></param>
		/// <param name="obj"></param>
		public void SetParent(Row parent, IForeignObject obj)
		{
			parentObject = parent;
			parentField = obj;
		}

		internal bool _StandAloneMode = false;

		/// <summary>
		/// Get join prefix for table in select query
		/// </summary>
		public string JoinPrefix
		{
			get
			{
				if (_StandAloneMode || ParentObject == null || ParentField == null)
					return "";
				return parentField.GetJoinPrefix();
			}
		}

		/// <summary>
		/// Get select prefix for child fields
		/// </summary>
		public string SelectPrefix
		{
			get
			{
				if (ParentObject == null || ParentField == null)
					return "";
				return ParentObject.SelectPrefix + ParentField.Name + Connection.FieldNameDelimiter;
			}
		}

		/// <summary>
		/// Get table name in select query
		/// </summary>
		public string JoinTableName
		{
			get
			{
				return JoinPrefix + TableName;
			}
		}

		/// <summary>
		/// Fields that was manually assigned
		/// </summary>
		public List<IField> AssignedFields
		{
			get
			{
				List<IField> assignedFields = new List<IField>();
				foreach (KeyValuePair<string, IField> pair in Fields)
				{
					IField field = pair.Value;
					if (field.IsManualSet())
						assignedFields.Add(field);
					if (field is IForeignObject)
					{
						IForeignObject fo = (IForeignObject)field;
						if (fo.IsForeignObjectCreated)
						{
							assignedFields.AddRange(fo.GetForeignObject().AssignedFields);
						}
					}
				}
				return assignedFields;
			}
		}

		#endregion

		#region Public functions

		#region Exists

		/// <summary>
		/// Check if at least one object exists with such assigned fields
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public bool Exists(Connection conn)
		{
			return Exists(conn, AssignedFields);
		}

		/// <summary>
		/// Check if at least one object exists with such primary key
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="primaryKeyValue"></param>
		/// <returns></returns>
		public bool Exists(Connection conn, object primaryKeyValue)
		{
			return Exists(conn, PrimaryKeyField, primaryKeyValue);
		}

		///<summary>
		///</summary>
		///<param name="conn"></param>
		///<param name="fieldsToCheckBy"></param>
		///<returns></returns>
		public bool Exists(Connection conn, params Enum[] fieldsToCheckBy)
		{
			return Exists(conn, GetFieldsByPropertyNames(fieldsToCheckBy));
		}

		/// <summary>
		/// Check if at least one object exists with such column value
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fieldName"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public bool Exists(Connection conn, Enum fieldName, object value)
		{
			return Exists(conn, GetFieldByPropertyName(fieldName), value);
		}

		///<summary>
		/// Check if at least one object exists with such column value
		///</summary>
		///<param name="conn"></param>
		///<param name="field"></param>
		///<param name="value"></param>
		///<returns></returns>
		public bool Exists(Connection conn, IField field, object value)
		{
			try
			{
				_StandAloneMode = true;
				QueryBuilder qb = new QueryBuilder(TableName_br);
				qb.Select.Add("COUNT(*)");
				qb.AddWhereCondition(conn, field, value);
				return Convert.ToInt32(conn.ExecuteScalar(qb)) > 0;
			}
			finally
			{
				_StandAloneMode = false;
			}
		}

		/// <summary>
		/// Check existence by specified fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		public bool Exists(Connection conn, IEnumerable<IField> fields)
		{
			return Convert.ToInt32(conn.ExecuteScalar(GetCountQB(conn, fields))) > 0;
		}

		#endregion

		#region GetCount

		/// <summary>
		/// Get objects count with currently assigned fields
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public int GetCount(Connection conn)
		{
			return Convert.ToInt32(conn.ExecuteScalar(GetCountQB(conn, AssignedFields)));
		}

		/// <summary>
		/// Get QueryBuilder for count
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public QueryBuilder GetCountQB(Connection conn)
		{
			return GetCountQB(conn, AssignedFields);
		}

		/// <summary>
		/// Get QueryBuilder for count
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		public QueryBuilder GetCountQB(Connection conn, IEnumerable<IField> fields)
		{
			try
			{
				_StandAloneMode = true;
				QueryBuilder qb = GetQueryBuilderForSelect(conn);
				qb.Select.Clear();
				qb.Select.Add("COUNT(*)");
				GetQueryBuilderForFields(conn, qb, fields);
				return qb;
			}
			finally
			{
				_StandAloneMode = false;
			}
		}

		#endregion

		#endregion

		#region Add/remove fields

		/// <summary>
		/// Add field
		/// </summary>
		/// <param name="field"></param>
		public void AddField(IField field)
		{
			Debug.Assert(!((field.Flags & FieldFlags.Identity) != 0 && (field.Flags & FieldFlags.Fictitious) != 0), "Fictitious field cannot have Identity");
			Debug.Assert(!((field.Flags & FieldFlags.PrimaryKey) != 0 && (field.Flags & FieldFlags.Fictitious) != 0), "Fictitious field cannot be primary key");
			// TODO: implement for other fields types decryption and encryption
			Debug.Assert(!((field.Flags & FieldFlags.Secure) != 0 && !(field is StringField)), "Only string field could be secure ");

			Fields.Add(field.Name, field);
			field.Parent = this;
		}

		/// <summary>
		/// Add field
		/// </summary>
		/// <param name="field"></param>
		public void AddField(IChildValuesField field)
		{
			ChildValuesFields.Add(field);
			field.Init(this);
		}

		/// <summary>
		/// Remove field
		/// </summary>
		/// <param name="field"></param>
		public void RemoveField(IField field)
		{
			Fields.Remove(field.Name);
		}

		/// <summary>
		/// Remove field
		/// </summary>
		/// <param name="field"></param>
		public void RemoveField(IChildValuesField field)
		{
			ChildValuesFields.Remove(field);
		}

		#endregion

		#region AutoLoad

		/// <summary>
		/// Load object
		/// </summary>
		public bool AutoLoad()
		{
			return AutoLoad(autoLoadMode_Connection);
		}

		/// <summary>
		/// Load object
		/// </summary>
		/// <param name="conn"></param>
		public bool AutoLoad(Connection conn)
		{
			if (autoLoadMode_IsLoaded)
				return true;

			if (conn == null)
				return false;

			if (ParentObject != null)
				ParentObject.AutoLoad(conn);

			Connection cprev = autoLoadMode_Connection;
			autoLoadMode_Connection = null;
			autoLoadMode_IsLoaded = LoadByPrimaryKey(conn) == OperationResult.Success;
			autoLoadMode_Connection = cprev;
			return autoLoadMode_IsLoaded;
		}

		internal Connection autoLoadMode_Connection;
		internal bool autoLoadMode_IsLoaded = false;

		/// <summary>
		/// Set AutoLoad mode
		/// </summary>
		/// <param name="conn"></param>
		public void SetAutoLoadMode(Connection conn)
		{
			autoLoadMode_Connection = conn;
		}

		#endregion

		#region Fields related

		/// <summary>
		/// Add fields to load
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="fieldsToLoad"></param>
		public void AddFieldsToLoad(Enum foreignField, params Enum[] fieldsToLoad)
		{
			string foreignFieldName = GetFieldNameByPropertyName(foreignField.ToString());
			if (string.IsNullOrEmpty(foreignFieldName))
				throw new InvalidOperationException("Cannot find foreign field by property name - " + foreignField);
			IForeignObject foreignObjectField;
			if (Fields.ContainsKey(foreignFieldName))
			{
				if (!(Fields[foreignFieldName] is IForeignObject))
					throw new InvalidOperationException("Specified foreign field property name is not IForeignObject - " + foreignField);
				foreignObjectField = (IForeignObject)Fields[foreignFieldName];
			}
			else throw new InvalidOperationException("Cannot find foreign field by name - " + foreignFieldName);
			foreach (Enum fieldToLoad in fieldsToLoad)
			{
				// get related field
				IFieldBase field;
				Row foreignObject = foreignObjectField.GetForeignObject();
				string fieldName = foreignObject.GetFieldNameByPropertyName(fieldToLoad.ToString());
				if (string.IsNullOrEmpty(fieldName))
					throw new InvalidOperationException("Cannot find field by property name - " + fieldToLoad);
				if (foreignObject.Fields.ContainsKey(fieldName))
					field = foreignObject.Fields[fieldName];
				else
					throw new InvalidOperationException("Cannot find field by name - " + fieldName);
				
				// check if upper-level fields were added
				CheckParentFieldWereAdded(foreignObjectField);

				// add if it was not added before
				if (!foreignObjectField.FieldsToLoad.Contains(field))
					foreignObjectField.FieldsToLoad.Add(field);
			}
		}

		private static void CheckParentFieldWereAdded(IFieldBase field)
		{
			IForeignObject ff = field.Parent.ParentField;
			if (ff == null || field.Parent._StandAloneMode)
				return;
			if (!ff.FieldsToLoad.Contains(field))
				ff.FieldsToLoad.Add(field);
			CheckParentFieldWereAdded(ff);
		}


		/// <summary>
		/// Compare with another object by primary key
		/// </summary>
		/// <param name="obj"></param>
		/// <returns>true if objects have eaqual primary keys</returns>
		public bool CompareByPrimaryKey(Row obj)
		{
			IField[] pk1 = PrimaryKeyFields;
			IField[] pk2 = obj.PrimaryKeyFields;
			if (pk1.Length != pk2.Length)
				return false;
			for (int i = 0; i < pk1.Length; i++)
			{
				if (pk1[i].Name != pk2[i].Name)
					return false;
				if (!pk1[i].GetValue().Equals(pk2[i].GetValue()))
					return false;
			}
			return true;
		}

		/// <summary>
		/// Compare with another object by assigned fields
		/// </summary>
		/// <param name="obj"></param>
		/// <returns>true if objects have eaqual primaru keys</returns>
		public bool CompareByAssignedFields(Row obj)
		{
			List<IField> fields = AssignedFields;
			foreach (IField field in fields)
			{
				if (!field.GetValue().Equals(obj.Fields[field.Name].GetValue()))
					return false;
			}
			return true;
		}

		private IField GetField(string complexName)
		{
			if (Fields.ContainsKey(complexName))
				return Fields[complexName];

			int k = complexName.IndexOf(Connection.FieldNameDelimiter);
			if (k == -1)
				return null;

			string foreignFieldName = complexName.Substring(0, k);
			if (!Fields.ContainsKey(foreignFieldName))
				return null;

			IForeignObject foreignField = Fields[foreignFieldName] as IForeignObject;
			if (foreignField == null)
				return null;

			return foreignField.GetForeignObject().GetField(complexName.Substring(k + 1));
		}

		/// <summary>
		/// Get field value
		/// </summary>
		/// <param name="fieldName"></param>
		/// <returns></returns>
		public object GetFieldValue(string fieldName)
		{
			IField field;
			if (Fields.TryGetValue(fieldName, out field))
				return field.GetValue();

			int k = fieldName.IndexOf(Connection.FieldNameDelimiter);
			if (k != -1)
			{
				string foreignFieldName = fieldName.Substring(0, k);
				if (Fields.ContainsKey(foreignFieldName))
				{
					IForeignObject foreignField = Fields[foreignFieldName] as IForeignObject;
					if (foreignField != null && Fields[foreignFieldName].GetValue() != null)
					{
						return foreignField.GetForeignObject().GetFieldValue(fieldName.Substring(k + 1));
					}
				}
			}

			throw new ArgumentException(string.Format("Wrong fieldName \"{0}\"", fieldName));
		}

		/// <summary>
		/// Check if primary key(s) is initialized
		/// </summary>
		public bool IsPrimaryKeyInitialized
		{
			get
			{
				foreach (IField primaryKeyField in PrimaryKeyFields)
				{
					if (!primaryKeyField.IsInitialized())
						return false;
				}
				return true;
			}
		}

		#endregion

		#region Virtual functions

		/// <summary>
		/// Useful for Views when need to insert field that is not in View
		/// </summary>
		/// <returns></returns>
		protected virtual string GetTableForInsert()
		{
			return TableName_br;
		}

		/// <summary>
		/// Called before load action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnLoad(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Called before update action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnUpdate(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Called before insert action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnInsert(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Called after load action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnLoaded(Connection conn)
		{
			return OperationResult.Success;
		}

		/// <summary>
		/// Called before delete action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnDelete(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Called after update action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnUpdated(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Called after insert action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnInserted(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Called after delete action. No need to call base.
		/// </summary>
		protected virtual OperationResult OnDeleted(Connection conn) { return OperationResult.Success; }

		/// <summary>
		/// Get QueryBuilder for SELECT query
		/// </summary>
		/// <param name="conn">conn instance</param>
		/// <returns></returns>
		public virtual QueryBuilder GetQueryBuilderForSelect(Connection conn)
		{
			Row parentObject_ = parentObject;
			IForeignObject parentField_ = parentField;
			parentObject = null;
			parentField = null;

			QueryBuilder qb = new QueryBuilder(TableName, TableAlias);

			HelperFunctions.AddToQueryBuilder(qb, Fields.Values, IgnoreHeavyFieldsOnLoad);

			parentObject = parentObject_;
			parentField = parentField_;
			return qb;
		}

		///<summary>
		/// Get QueryBuilder for UPDATE query
		///</summary>
		///<param name="conn"></param>
		///<param name="addPrimaryKeys"></param>
		///<returns></returns>
		public virtual QueryBuilder GetQueryBuilderForUpdate(Connection conn, bool addPrimaryKeys)
		{
			return GetQueryBuilderForUpdate(conn, addPrimaryKeys, true);
		}

		/// <summary>
		/// Get QueryBuilder for UPDATE query
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="addPrimaryKeys"></param>
		/// <param name="updateOnlyManualAssigned"></param>
		/// <returns></returns>
		public virtual QueryBuilder GetQueryBuilderForUpdate(Connection conn, bool addPrimaryKeys, bool updateOnlyManualAssigned)
		{
			int manualAssignedCount;
			return GetQueryBuilderForUpdate_impl(conn, addPrimaryKeys ? PrimaryKeyFields : new IField[] { }, out manualAssignedCount, updateOnlyManualAssigned);
		}

		/// <summary>
		/// Get QueryBuilder for SELECT query
		/// </summary>
		/// <param name="conn">conn instance</param>
		/// <param name="fieldNames"></param>
		/// <returns></returns>
		public virtual QueryBuilder GetQueryBuilderForGivenFields(Connection conn, params Enum[] fieldNames)
		{
			Row parentObject_ = parentObject;
			IForeignObject parentField_ = parentField;
			parentObject = null;
			parentField = null;

			QueryBuilder qb = new QueryBuilder(TableName, TableAlias);

			foreach (Enum fieldName in fieldNames)
			{
				GetFieldByPropertyName(fieldName).AddToSelectQuery(qb);
			}

			parentObject = parentObject_;
			parentField = parentField_;
			return qb;
		}

		///<summary>
		///</summary>
		///<param name="fieldNames"></param>
		///<returns></returns>
		public QueryBuilder GetQueryBuilderForGivenFields(params Enum[] fieldNames)
		{
			// simplified version
			// TODO: change to static
			QueryBuilder qb = new QueryBuilder(TableName_br, TableAlias);

			foreach (Enum fieldName in fieldNames)
			{
				GetFieldByPropertyName(fieldName).AddToSelectQuery(qb);
			}

			return qb;
		}

		/// <summary>
		/// By default if error occures, last exception is saved and EventHandler.OnDatabaseException called
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="ex"></param>
		protected virtual void OnErrorOccured(Connection conn, DatabaseException ex)
		{
			LastException = ex;
			conn.OnDatabaseException(ex);
		}

		#endregion

		#region Public functions related to Load/Save/Insert/Delete/Update

		#region Static functions for Load

		/// <summary>
		/// Load by primary key (static version)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="conn"></param>
		/// <param name="primaryKey"></param>
		/// <returns></returns>
		public static T LoadByPrimaryKey<T>(Connection conn, object primaryKey) where T : Row, new()
		{
			T obj = new T();
			return obj.LoadByPrimaryKey(conn, primaryKey) == OperationResult.Success ? obj : null;
		}

		///<summary>
		/// Load the only record in table (static version)
		///</summary>
		///<param name="conn"></param>
		///<typeparam name="T"></typeparam>
		///<returns></returns>
		/// <remarks>as usual used for global settings</remarks>
		public static T LoadSingltonObject<T>(Connection conn) where T : Row, new()
		{
			T obj = new T();
			return obj.LoadByAssignedFields(conn) == OperationResult.Success ? obj : null;
		}

		#endregion

		#region Get QB for Load

		/// <summary>
		/// Get query builder for assigned fields
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public QueryBuilder GetLoadByAssignedFieldsQB(Connection conn)
		{
			return GetQueryBuilderForFields(conn, AssignedFields);
		}

		#endregion

		#region Raw Load Functions

		/// <summary>
		/// Load by query builder
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="qb"></param>
		/// <returns></returns>
		public OperationResult LoadByQueryBuilder(Connection conn, QueryBuilder qb)
		{
			return LoadBySQL(conn, qb.ToString(), qb.Parameters.ToArray());
		}

		/// <summary>
		/// Load by given sql
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public OperationResult LoadBySQL(Connection conn, string query, params IDataParameter[] parameters)
		{
			_StandAloneMode = true;
			ClearInnerState();
			DatabaseException exception;
			try
			{
				OperationResult result = OnLoad(conn);
				if (result != OperationResult.Success)
					return result;
				using (IDataReader reader = conn.ExecuteDataReader(query, parameters))
				{
					if (!reader.Read())
						return OperationResult.EmptyResult;
					Load(conn, reader);
					if (reader.Read())
						return OperationResult.MultipleResult;
				}

				if (childValuesFields != null)
				{
					foreach (IChildValuesField childValuesField in childValuesFields)
					{
						result = childValuesField.OnLoad(conn);
						if (result != OperationResult.Success)
							return result;
					}
				}

				result = OnLoaded(conn);
				if (result != OperationResult.Success)
					return result;

				return OperationResult.Success;
			}
			catch (DatabaseException ex)
			{
				exception = ex;
			}
			catch (Exception ex)
			{
				exception = new DatabaseException("", ex, "Row.Load(Connection, Query, Parameters)", GetType());
			}
			OnErrorOccured(conn, exception);
			return OperationResult.ExceptionOccured;
		}

		#endregion

		#region LoadBy.. Functions

		/// <summary>
		/// Load by primary key
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public OperationResult LoadByPrimaryKey(Connection conn, object value)
		{
			return LoadByField(conn, PrimaryKeyField, value);
		}

		/// <summary>
		/// Load by primary key
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public OperationResult LoadByPrimaryKey(Connection conn)
		{
			return LoadByFields(conn, PrimaryKeyFields);
		}

		/// <summary>
		/// Load primary key by assigned/given fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fieldsBy">if not specified load by assigned otherwise by given fields</param>
		/// <returns></returns>
		public OperationResult LoadPrimaryKey(Connection conn, params Enum[] fieldsBy)
		{
			_StandAloneMode = true;
			ClearInnerState();
			DatabaseException exception;
			try
			{
				QueryBuilder qb = new QueryBuilder(TableName, TableAlias);
				foreach (IField keyField in PrimaryKeyFields)
				{
					keyField.AddToSelectQuery(qb);
				}
				GetQueryBuilderForFields(conn, qb,
					fieldsBy == null || fieldsBy.Length == 0 ? AssignedFields : GetFieldsByPropertyNames(fieldsBy));
				return LoadByQueryBuilder(conn, qb);
			}
			catch (DatabaseException ex)
			{
				exception = ex;
			}
			catch (Exception ex)
			{
				exception = new DatabaseException("", ex, "Row.LoadPrimaryKey", GetType());
			}
			OnErrorOccured(conn, exception);
			return OperationResult.ExceptionOccured;
		}

		/// <summary>
		/// Load by field with assigned values
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public OperationResult LoadByAssignedFields(Connection conn)
		{
			return LoadByFields(conn, AssignedFields);
		}

		#endregion

		#region LoadFields.. Functions

		///<summary>
		/// Load only specified field by assigned fields
		///</summary>
		///<param name="conn"></param>
		///<param name="fieldNames"></param>
		///<returns></returns>
		public OperationResult LoadFieldsByAssigned(Connection conn, params Enum[] fieldNames)
		{
			if (fieldNames.Length == 0)
				throw new DatabaseException("No fields to load", null, "Row.LoadFieldsByAssigned", GetType());
			_StandAloneMode = true;
			QueryBuilder qb = GetQueryBuilderForFields(conn, GetQueryBuilderForGivenFields(conn, fieldNames), AssignedFields);
			return LoadByQueryBuilder(conn, qb);
		}

		///<summary>
		/// Load only specified field by primary key(s)
		///</summary>
		///<param name="conn"></param>
		///<param name="fieldNames"></param>
		///<returns></returns>
		public OperationResult LoadFieldsByPrimary(Connection conn, params Enum[] fieldNames)
		{
			if (fieldNames.Length == 0)
				throw new DatabaseException("No fields to load", null, "Row.LoadFieldsByPrimary", GetType());
			_StandAloneMode = true;
			QueryBuilder qb = GetQueryBuilderForFields(conn, GetQueryBuilderForGivenFields(conn, fieldNames), PrimaryKeyFields);
			return LoadByQueryBuilder(conn, qb);
		}

		///<summary>
		/// Load only specified field by primary key
		///</summary>
		///<param name="conn"></param>
		///<param name="primaryKey"></param>
		///<param name="fieldNames"></param>
		///<returns></returns>
		public OperationResult LoadFieldsByPrimary(Connection conn, object primaryKey, params Enum[] fieldNames)
		{
			if (fieldNames.Length == 0)
				throw new DatabaseException("No fields to load", null, "Row.LoadFieldsByPrimary", GetType());
			_StandAloneMode = true;
			PrimaryKeyField.InitValue(primaryKey);
			QueryBuilder qb = GetQueryBuilderForFields(conn, GetQueryBuilderForGivenFields(conn, fieldNames), PrimaryKeyFields);
			return LoadByQueryBuilder(conn, qb);
		}

		#endregion

		#region Delete

		/// <summary>
		/// Delete by primary key
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="primaryKeyValue"></param>
		/// <returns></returns>
		public OperationResult Delete(Connection conn, object primaryKeyValue)
		{
			_StandAloneMode = true;
			PrimaryKeyField.InitValue(primaryKeyValue);
			return Delete(conn);
		}

		/// <summary>
		/// Delete by primary key
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public OperationResult Delete(Connection conn)
		{
			return DeleteByFields(conn, PrimaryKeyFields, true);
		}

		///<summary>
		/// Delete by specified fields
		///</summary>
		///<param name="conn"></param>
		///<param name="fieldNames"></param>
		///<returns></returns>
		public OperationResult DeleteByFields(Connection conn, params Enum[] fieldNames)
		{
			return DeleteByFields(conn, GetFieldsByPropertyNames(fieldNames), false);
		}

		/// <summary>
		/// Delete by assigned fields
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public OperationResult DeleteByAssignedFields(Connection conn)
		{
			return DeleteByFields(conn, AssignedFields, false);
		}

		#endregion

		#region Insert

		/// <summary>
		/// Insert object
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public OperationResult Insert(Connection conn)
		{
			return Insert(conn, true);
		}

		/// <summary>
		/// Insert object
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="insertOnlyAssigned"></param>
		/// <returns></returns>
		public OperationResult Insert(Connection conn, bool insertOnlyAssigned)
		{
			_StandAloneMode = true;
			ClearInnerState();
			DatabaseException exception;
			try
			{
				OperationResult result = OnInsert(conn);
				if (result != OperationResult.Success)
					return result;
				StringBuilder sbFields = new StringBuilder();
				StringBuilder sbValues = new StringBuilder();
				int count = 0;
				List<IDataParameter> parameters = new List<IDataParameter>(Fields.Count);
				IField identityField = null;
				foreach (KeyValuePair<string, IField> field in Fields)
				{
					if ((field.Value.Flags & FieldFlags.Fictitious) != 0)
						continue;
					if ((field.Value.Flags & FieldFlags.Identity) != 0)
					{
						if (identityField != null)
							throw new DatabaseException("Cannot have several fields with identity", null, "Row.Insert(conn)", GetType());
						identityField = field.Value;
						continue;
					}
					bool set = false;
					if (!(field.Value.IsManualSet() || (!insertOnlyAssigned && field.Value.IsInitialized())))
					{
						set = field.Value.SetCreateAutoValue(conn);
					}
					if (!set && !field.Value.IsInitialized())
					{
						if ((field.Value.Flags & FieldFlags.DisableNull) != 0)
							throw new DatabaseException("Null value is not allowed for field " + field.Value.Name, null, "Row.Insert(conn)", GetType());
						continue;
					}
					if (count > 0)
					{
						sbFields.Append(", ");
						sbValues.Append(", ");
					}
					object fieldValue = field.Value.GetValue();
					if (fieldValue == null && (field.Value.Flags & FieldFlags.DisableNull) != 0)
						throw new DatabaseException("Null value is not allowed for field " + field.Value.Name, null, "Row.Insert(conn)", GetType());
					IDataParameter parameter = conn.CreateParameter(field.Key, field.Value);
					parameters.Add(parameter);
					sbFields.AppendFormat("[{0}]", field.Key);
					sbValues.Append(parameter.ParameterName);
					count++;
				}
				if (count == 0)
					throw new DatabaseException("Nothing to insert", null, "Row.Insert(conn)", GetType());

				string query = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", GetTableForInsert(), sbFields, sbValues);
				if (identityField != null)
				{
					object value;
					if (conn.ProviderType == ProviderType.MSSQL)
					{
						value = conn.ExecuteScalar(query + ";" + "SELECT @@IDENTITY", parameters.ToArray());
					}
					else
					{
						conn.ExecuteNonQuery(query, parameters.ToArray());
						value = conn.ExecuteScalar("SELECT @@IDENTITY");
					}
					identityField.InitValue(value);
				}
				else
				{
					conn.ExecuteNonQuery(query, parameters.ToArray());
				}

				if (childValuesFields != null)
				{
					foreach (IChildValuesField childValuesField in childValuesFields)
					{
						if (childValuesField.ReadOnly) 
							continue;
						result = childValuesField.OnInsert(conn);
						if (result != OperationResult.Success)
							return result;
					}
				}

				return OnInserted(conn);
			}
			catch (DatabaseException ex)
			{
				exception = ex;
			}
			catch (Exception ex)
			{
				exception = new DatabaseException("", ex, "Row.Insert(Connection)", GetType());
			}
			OnErrorOccured(conn, exception);
			return OperationResult.ExceptionOccured;
		}

		#endregion

		#region Update

		/// <summary>
		/// Update object by primary keys
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult Update(Connection conn)
		{
			return UpdateByFields(conn, PrimaryKeyFields);
		}

		/// <summary>
		/// Update object by primary keys
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="updateOnlyManualAssigned"></param>
		/// <returns></returns>
		public virtual OperationResult Update(Connection conn, bool updateOnlyManualAssigned)
		{
			return UpdateByFields(conn, updateOnlyManualAssigned, PrimaryKeyFields);
		}

		/// <summary>
		/// Update object by given fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		public virtual OperationResult UpdateByFields(Connection conn, params Enum[] fields)
		{
			return UpdateByFields(conn, true, fields);
		}

		/// <summary>
		/// Update object by given fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="updateOnlyManualAssigned"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		public virtual OperationResult UpdateByFields(Connection conn, bool updateOnlyManualAssigned, params Enum[] fields)
		{
			return UpdateByFields(conn, updateOnlyManualAssigned, GetFieldsByPropertyNames(fields).ToArray());
		}

		/// <summary>
		/// Update set of objects
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="obj"></param>
		/// <returns></returns>
		public OperationResult UpdateFrom(Connection conn, Row obj)
		{
			_StandAloneMode = true;
			DatabaseException exception;
			try
			{
				List<IField> fieldsToUpdate = AssignedFields;
				List<IField> conditionFields = obj.AssignedFields;
				QueryBuilder qb = new QueryBuilder(QueryBuilderType.Update, TableName);

				foreach (IField field in fieldsToUpdate)
				{
					if ((field.Flags & FieldFlags.Fictitious) != 0 || (field.Flags & FieldFlags.Identity) != 0) continue;
					IDataParameter par = conn.CreateParameter(field.Name, field);
					qb.AddParameter(par);
					qb.Update.Add(field.Name + "=" + par.ParameterName);
				}

				foreach (IField field in conditionFields)
				{
					IDataParameter par = conn.CreateParameter(field.Name + "_w", field);
					qb.AddParameter(par);
					qb.Where.Add(field.Name + "=" + par.ParameterName);
				}

				conn.ExecuteNonQuery(qb);
				return OperationResult.Success;
			}
			catch (DatabaseException ex)
			{
				exception = ex;
			}
			catch (Exception ex)
			{
				exception = new DatabaseException("", ex, "Row.UpdateFrom", GetType());
			}
			OnErrorOccured(conn, exception);
			return OperationResult.ExceptionOccured;
		}

		#endregion

		#region Insert/Update

		/// <summary>
		/// Determine if need to update or insert. Supported only for one PK with identity. Do insert if PK is not initialized.
		/// </summary>
		/// <returns>Return true if Save function will do inserting</returns>
		public bool WillDoInserting()
		{
			IField[] pks = PrimaryKeyFields;

			if (pks.Length != 1)
				throw new InvalidOperationException("Illegial use of WillDoInserting function (support only PK count = 1)");

			IField pk = pks[0];
			if ((pk.Flags & FieldFlags.Identity) == 0)
				throw new InvalidOperationException("Illegial use of WillDoInserting function (support only for PK with identity)");

			return !pk.IsInitialized();
		}

		/// <summary>
		/// Save object
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public OperationResult Save(Connection conn)
		{
			return Save(conn, WillDoInserting());
		}

		/// <summary>
		/// Save object
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="doInsert">do insert or update</param>
		/// <returns></returns>
		public OperationResult Save(Connection conn, bool doInsert)
		{
			return doInsert ? Insert(conn) : Update(conn);
		}

		#endregion

		#endregion

		#region Protected functions

		/// <summary>
		/// Update by specified fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="updateByFields"></param>
		/// <returns></returns>
		protected internal OperationResult UpdateByFields(Connection conn, params IField[] updateByFields)
		{
			return UpdateByFields(conn, true, updateByFields);
		}

		/// <summary>
		/// Update by specified fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="updateOnlyManualAssigned"></param>
		/// <param name="updateByFields"></param>
		/// <returns></returns>
		protected internal OperationResult UpdateByFields(Connection conn, bool updateOnlyManualAssigned, params IField[] updateByFields)
		{
			DatabaseException exception;
			try
			{
				_StandAloneMode = true;
				ClearInnerState();
				// on update
				OperationResult result = OnUpdate(conn);
				if (result != OperationResult.Success)
					return result;

				// UPDATE
				int manualAssignedCount;
				QueryBuilder qb = GetQueryBuilderForUpdate_impl(conn, updateByFields, out manualAssignedCount, updateOnlyManualAssigned);
				if (qb.Update.Count == 0)
					throw new DatabaseException("Nothing to update", null, "Row.UpdateByFields", GetType());
				if (manualAssignedCount == 0)
					conn.OnDatabaseWarning(new DatabaseWarning("Update only auto-fields", "Row.UpdateByFields", GetType()));
				if (conn.ExecuteNonQuery(qb) == 0)
					return OperationResult.NothingForUpdate;

				// on updated
				if (updateByFields.Length == PrimaryKeyFields.Length)
				{
					bool isPrimary = true;
					foreach (IField field in primaryKeyFields)
					{
						foreach (IField updateByField in updateByFields)
						{
							if (updateByField.Name != field.Name)
							{
								isPrimary = false;
								break;
							}
						}
					}
					if (isPrimary)
					{
						if (childValuesFields != null)
						{
							foreach (IChildValuesField childValuesField in childValuesFields)
							{
								if (childValuesField.ReadOnly || !childValuesField.IsInitialized) 
									continue;
								result = childValuesField.OnUpdate(conn);
								if (result != OperationResult.Success)
									return result;
							}
						}
					}
				}

				return OnUpdated(conn);
			}
			catch (DatabaseException ex)
			{
				exception = ex;
			}
			catch (Exception ex)
			{
				exception = new DatabaseException("", ex, "Row.UpdateByFields(Connection)", GetType());
			}
			finally
			{
				_StandAloneMode = false;
			}
			OnErrorOccured(conn, exception);
			return OperationResult.ExceptionOccured;
		}

		private QueryBuilder GetQueryBuilderForUpdate_impl(Connection conn, IEnumerable<IField> updateByFields, out int manualAssignedCount, bool updateOnlyManualAssigned)
		{
			Dictionary<string, IField> updateByFieldsDictinary = new Dictionary<string, IField>();
			foreach (IField field in updateByFields)
			{
				updateByFieldsDictinary.Add(field.Name, field);
			}

			QueryBuilder qb = new QueryBuilder(QueryBuilderType.Update, TableName);

			manualAssignedCount = 0;
			foreach (KeyValuePair<string, IField> pair in Fields)
			{
				if ((pair.Value.Flags & FieldFlags.Fictitious) != 0 ||
					 (pair.Value.Flags & FieldFlags.Identity) != 0 ||
					 updateByFieldsDictinary.ContainsKey(pair.Key))
					continue;
				if ((updateOnlyManualAssigned && !pair.Value.IsManualSet())
					 || (!updateOnlyManualAssigned && !pair.Value.IsInitialized()))
				{
					if (!pair.Value.SetUpdateAutoValue(conn))
						continue;
				}
				else manualAssignedCount++;

				if (!pair.Value.IsInitialized())
					throw new DatabaseException("'Update by' fields should be initialized", null, "Row.GetQueryBuilderForUpdate_impl", GetType());

				object fieldValue = pair.Value.GetValue();
				if (fieldValue == null && (pair.Value.Flags & FieldFlags.DisableNull) != 0)
					throw new DatabaseException("Null value is not allowed for field " + pair.Value.Name, null, "Row.GetQueryBuilderForUpdate_impl", GetType());

				qb.AddUpdate(conn, pair.Value);
			}

			foreach (IField field in updateByFields)
			{
				qb.AddWhereCondition(conn, field, field.GetValue());
			}

			return qb;
		}

		/// <summary>
		/// Load by fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		protected internal OperationResult LoadByFields(Connection conn, IEnumerable<IField> fields)
		{
			_StandAloneMode = true;
			return LoadByQueryBuilder(conn, GetQueryBuilderForFields(conn, fields));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		protected internal QueryBuilder GetQueryBuilderForFields(Connection conn, IEnumerable<IField> fields)
		{
			return GetQueryBuilderForFields(conn, GetQueryBuilderForSelect(conn), fields);
		}

		///<summary>
		/// Add where conditions for given QueryBuilder
		///</summary>
		///<param name="conn"></param>
		///<param name="qb"></param>
		///<param name="fields"></param>
		///<returns></returns>
		public QueryBuilder GetQueryBuilderForFields(Connection conn, QueryBuilder qb, params Enum[] fields)
		{
			foreach (Enum fieldName in fields)
			{
				IField field = GetFieldByPropertyName(fieldName);
				qb.AddWhereCondition(conn, field, field.GetValue());
			}
			return qb;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="qb"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		internal protected QueryBuilder GetQueryBuilderForFields(Connection conn, QueryBuilder qb, IEnumerable<IField> fields)
		{
			foreach (IField field in fields)
			{
				qb.AddWhereCondition(conn, field, field.GetValue());
			}
			return qb;
		}

		/// <summary>
		/// Delete by primary key
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="deleteByFields"></param>
		/// <param name="callOnDelete"></param>
		/// <returns></returns>
		protected internal OperationResult DeleteByFields(Connection conn, IEnumerable<IField> deleteByFields, bool callOnDelete)
		{
			_StandAloneMode = true;
			ClearInnerState();
			DatabaseException exception;
			try
			{
				OperationResult result;
				if (callOnDelete)
				{
					result = OnDelete(conn);
					if (result != OperationResult.Success)
						return result;
					if (childValuesFields != null)
					{
						foreach (IChildValuesField childValuesField in childValuesFields)
						{
							if (childValuesField.ReadOnly) 
								continue;
							result = childValuesField.OnBeforeDelete(conn);
							if (result != OperationResult.Success)
								return result;
						}
					}
				}

				QueryBuilder qb = new QueryBuilder(QueryBuilderType.Delete, TableName);
				foreach (IField field in deleteByFields)
				{
					if (!field.IsInitialized())
						throw new DatabaseException("Delete: field " + field.Name + "is not initialized", null, "Row.Delete(Connection)", GetType());
					qb.AddWhereEqualCondition(conn, field);
				}

				/*int rowsChanged = */
				conn.ExecuteNonQuery(qb);

				if (callOnDelete)
				{
					if (childValuesFields != null)
					{
						foreach (IChildValuesField childValuesField in childValuesFields)
						{
							if (childValuesField.ReadOnly) 
								continue;
							result = childValuesField.OnDelete(conn);
							if (result != OperationResult.Success)
								return result;
						}
					}

					return OnDeleted(conn);
				}

				return OperationResult.Success;
			}
			catch (DatabaseException ex)
			{
				exception = ex;
			}
			catch (Exception ex)
			{
				exception = new DatabaseException("", ex, "Row.Delete(Connection)", GetType());
			}
			finally
			{
				_StandAloneMode = false;
			}
			OnErrorOccured(conn, exception);
			return OperationResult.ExceptionOccured;
		}

		/// <summary>
		/// Load by field
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="keyField"></param>
		/// <returns></returns>
		protected internal OperationResult LoadByField(Connection conn, IField keyField)
		{
			return LoadByFields(conn, new[] { keyField });
		}

		/// <summary>
		/// Load by fields
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="keyField"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		protected internal OperationResult LoadByField(Connection conn, IField keyField, object value)
		{
			keyField.InitValue(value);
			return LoadByField(conn, keyField);
		}

		internal void Load(Connection conn, IDataReader reader)
		{
			ClearInnerState();
			for (int i = 0; i < reader.FieldCount; i++)
			{
				string fieldName = reader.GetName(i);
				if (!SetFieldValue(fieldName, reader[i], false))
					throw new DatabaseException(string.Format("No corresponding field for selected object [{0}]", reader.GetName(i)), null, "Row.Load(Connection, Reader)", GetType());
			}
		}

		/// <summary>
		/// Clear inner state
		/// </summary>
		protected void ClearInnerState()
		{
			LastException = null;
		}

		#endregion

		#region Public manipulations with fields

		///<summary>
		/// Check if field is initialized
		///</summary>
		///<param name="field"></param>
		///<returns></returns>
		public bool IsFieldInitialized(Enum field)
		{
			return GetFieldByPropertyName(field).IsInitialized();
		}

		///<summary>
		/// Init field
		///</summary>
		///<param name="field"></param>
		///<param name="value"></param>
		public void InitField(Enum field, object value)
		{
			GetFieldByPropertyName(field).InitValue(value);
		}

		///<summary>
		/// If field is not initialized, init it with gived value
		///</summary>
		///<param name="fieldName"></param>
		///<param name="value"></param>
		public void EnsureFieldInitialized(Enum fieldName, object value)
		{
			IField field = GetFieldByPropertyName(fieldName);
			if (!field.IsInitialized())
				field.InitValue(value);
		}

		/// <summary>
		/// Set field value
		/// </summary>
		/// <param name="fieldName"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public bool SetFieldValue(string fieldName, object value)
		{
			return SetFieldValue(fieldName, value, true);
		}

		/// <summary>
		/// Set field value
		/// </summary>
		/// <param name="fieldName"></param>
		/// <param name="value"></param>
		/// <param name="set">set or init</param>
		/// <returns></returns>
		public bool SetFieldValue(string fieldName, object value, bool set)
		{
			if (Fields.ContainsKey(fieldName))
			{
				// Descypt Secure fields here
				object setValue = value;
				if (setValue != null && setValue != DBNull.Value)
				{
					if ((Fields[fieldName].Flags & FieldFlags.Secure) != 0)
					{
						setValue = (Fields[fieldName].Flags & FieldFlags.SecuredNoExtraBytes) != 0
						           	? DESHelper.DecryptTextNoExtraBytes((string) value)
						           	: DESHelper.DecryptText((string) value);
					}
					else if ((Fields[fieldName].Flags & FieldFlags.CustomDecrypt) != 0)
					{
						setValue = ((ICustomDecrypt)Fields[fieldName].Parent).Decrypt(Fields[fieldName], (string)value);
					}
				}

				if (set)
					Fields[fieldName].SetValue(setValue);
				else
					Fields[fieldName].InitValue(setValue);

				return true;
			}

			int k = fieldName.IndexOf(Connection.FieldNameDelimiter);
			if (k == -1)
				return false;

			string foreignFieldName = fieldName.Substring(0, k);
			if (!Fields.ContainsKey(foreignFieldName))
				return false;

			IForeignObject foreignField = Fields[foreignFieldName] as IForeignObject;
			if (foreignField == null)
				return false;

			if ((value != null && value != DBNull.Value) || foreignField.IsExist)
				return foreignField.GetForeignObject().SetFieldValue(fieldName.Substring(k + 1), value, set);

			IField field = foreignField.GetForeignObject().GetField(fieldName.Substring(k + 1));
			if (field == null)
				return false;

			Type type = field.GetValueType();
			if (type.IsGenericType || !type.IsValueType)
			{
				if (set)
					field.SetValue(null);
				else
					field.InitValue(null);
			}

			return true;
		}

		///<summary>
		///</summary>
		///<param name="field"></param>
		public virtual void ReplaceFieldInstance(IField field)
		{
			FieldNameToFieldMap[field.Name].SetValue(this, field);
		}

		#endregion

		#region Implementation of ICloneable

		/// <summary>
		/// Clone (light-weight by default - clone only added fields)
		/// </summary>
		/// <returns></returns>
		/// <remarks>
		/// By default there is no need to override this function
		/// </remarks>
		public virtual object Clone()
		{
			Row row = CreateObjectForClone();
			Debug.Assert(row.Fields.Count == 0);

			foreach (IField field in Fields.Values)
			{
				IField newField = (IField)field.Clone();
				row.AddField(newField);
				row.ReplaceFieldInstance(newField);
			}

			return row;
		}

		#endregion

		#region Public properties

		private bool _ignoreHeavyFieldsOnLoad = true;

		/// <summary>
		/// 
		/// </summary>
		public bool IgnoreHeavyFieldsOnLoad
		{
			get { return _ignoreHeavyFieldsOnLoad; }
			set { _ignoreHeavyFieldsOnLoad = value; }
		}

		#endregion
	}
}
